/**
 * Copyright (c) 2010-2020 by skzr.org
 * All rights reserved.
 * @Author skzr.org(E-mail:skzr.org@gmail.com)
 * @date 2010-4-23 上午11:31:10
 */
package org.skzr.proxy;


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.skzr.framework.util.UtilIO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;


/**
 * 输入数据格式为： method,url,header.size(),headers,content
 * 输出数据格式为： httpVersion,responseCode,codeStr,header.size(),headers,content
 * @jdk 1.6
 * @author skzr.org(E-mail:skzr.org@gmail.com)
 * @version 1.0.0
 */
public class FetchServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected final transient Logger logger = LoggerFactory.getLogger(getClass());
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		logger.debug("doGet");
	}
	
	@SuppressWarnings("unchecked")
	private void doPostByUrl(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		for (Enumeration<String> nameIter = req.getHeaderNames(); nameIter.hasMoreElements();) {
			String name = nameIter.nextElement();
			for (Enumeration<String> valueIter = req.getHeaders(name); valueIter.hasMoreElements();) {
				String value = valueIter.nextElement();
				logger.trace("[proxy] {} = {}", name, value);
			}
		}
		
		DataInputStream in = new DataInputStream((req.getInputStream()));
		String method = in.readUTF(); 
		String url = in.readUTF();
		String protocol = in.readUTF();
		logger.debug("[proxy][in ] {} {} {}", new String[]{method, url, protocol});
		
		int headerLen = in.readInt();
		logger.trace("[proxy][in ] headers.size() = {}", headerLen);
		
		List<String[]> headers = new ArrayList<String[]>(headerLen);
		for (int i = 0; i < headerLen; i++) {
			String name = in.readUTF(), value = in.readUTF();
			logger.trace("[proxy][in ][header] {}: {}", name, value);
			headers.add(new String[]{name, value});
		}
		
		URL reqUrl = new URL(url);
		HttpURLConnection con = (HttpURLConnection) reqUrl.openConnection();
		con.setRequestMethod(method);
		for (String[] h : headers) {
			con.addRequestProperty(h[0], h[1]);
		}

		if (!"GET".equals(method)) {
			con.setDoOutput(true);
			UtilIO.write(in, con.getOutputStream());
		}
		
		DataOutputStream out = new DataOutputStream(resp.getOutputStream());
		{
			protocol = reqUrl.getProtocol();
			int code = con.getResponseCode();
			String msg = con.getResponseMessage();
			logger.debug("[proxy][out] {} {} {}", new Object[]{protocol, code, msg});
			out.writeUTF(protocol); out.writeInt(code); out.writeUTF(msg);
		}
		{
			List<String[]> rheaders = new ArrayList<String[]>();
			{
				Map<String, List<String>> hfMap = con.getHeaderFields();
				for (Entry<String, List<String>> entry : hfMap.entrySet()) {
					for (String v : entry.getValue()) {
						logger.trace("[proxy][out][header] {}: {}", entry.getKey(), v);
						rheaders.add(new String[]{entry.getKey(), v});
					}
				}
			}
			out.writeInt(rheaders.size());
			for (String[] h : rheaders) {
				out.writeUTF(h[0]); out.writeUTF(h[1]);
			}
		}
		{
			InputStream content = con.getInputStream();
			if (content != null) UtilIO.write(content, out);
		}
		
		out.flush();

		logger.debug("[proxy] finish {} {}", method, url);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		if (req != resp) {
			doPostByUrl(req, resp);
			return;
		}
		for (Enumeration<String> nameIter = req.getHeaderNames(); nameIter.hasMoreElements();) {
			String name = nameIter.nextElement();
			for (Enumeration<String> valueIter = req.getHeaders(name); valueIter.hasMoreElements();) {
				String value = valueIter.nextElement();
				logger.trace("[proxy] {} = {}", name, value);
			}
		}
		
		DataInputStream in = new DataInputStream(new GZIPInputStream(req.getInputStream()));
		String method = in.readUTF(); 
		String url = in.readUTF();
		logger.debug("[proxy][in ] {} {}", method, url);
		
		int headerLen = in.readInt();
		logger.trace("[proxy][in ] headers.size() = {}", headerLen);
		
		List<String[]> headers = new ArrayList<String[]>(headerLen);
		for (int i = 0; i < headerLen; i++) {
			String name = in.readUTF(), value = in.readUTF();
			logger.trace("[proxy][in ] {} = {}", name, value);
			headers.add(new String[]{name, value});
		}
		
		HTTPRequest request = new HTTPRequest(new URL(url), HTTPMethod.valueOf(method), FetchOptions.Builder.disallowTruncate().doNotFollowRedirects());
		for (String[] header : headers) {
			request.addHeader(new HTTPHeader(header[0], header[1]));
		}
		
		if (!"GET".equals(method)) {
			request.setPayload(UtilIO.writeByteArrayOutputStream(in).toByteArray());
		}
		
		HTTPResponse response = URLFetchServiceFactory.getURLFetchService().fetch(request);
		List<HTTPHeader> hs = response.getHeaders();
		GZIPOutputStream gout = new GZIPOutputStream(resp.getOutputStream(), 500 * 1024);
		DataOutputStream out = new DataOutputStream(gout);
		out.writeInt(hs.size());
		for (HTTPHeader header : hs) {
			logger.trace("[proxy][out] {} = {}", header.getName(), header.getValue());
			out.writeUTF(header.getName());
			out.writeUTF(header.getValue());
		}
		logger.debug("[proxy][out] responseCode = {}", response.getResponseCode());
		out.writeInt(response.getResponseCode());
		byte[] content = response.getContent();
		if (content != null) out.write(content);
		out.flush();
		gout.close();
		logger.debug("[proxy] finish {} {}", method, url);
	}
}